/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain (a) copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.sentry.ohos.core;

import ohos.app.Context;

import io.sentry.IHub;
import io.sentry.ILogger;
import io.sentry.Integration;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.exception.ExceptionMechanismException;
import io.sentry.protocol.Mechanism;
import io.sentry.util.Objects;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

import java.io.Closeable;
import java.io.IOException;

/**
 * When the UI thread of an Ohos app is blocked for too long, an "Application Not Responding"
 * (ANR) error is triggered. Sends an event if an ANR happens
 *
 * @since 2021-06-01
 */
public final class AnrIntegration implements Integration, Closeable {
    private static final @NotNull Object WATCH_DOG_LOCK = new Object();
    /**
     * Responsible for watching UI thread. being static to avoid multiple instances running at the
     * same time.
     */

    // we have watchDogLock to avoid those leaks
    private static @Nullable ANRWatchDog anrWatchDog;

    private @Nullable SentryOptions options;

    private final @NotNull Context context;

    /**
     * AnrIntegration
     *
     * @param context Context
     */
    public AnrIntegration(final @NotNull Context context) {
        this.context = context;
    }

    @Override
    public void register(@NotNull IHub hub, @NotNull SentryOptions sentryOptions) {
        this.options = Objects.requireNonNull(sentryOptions, "SentryOptions is required");
        register(hub, (SentryOhosOptions) sentryOptions);
    }

    private void register(final @NotNull IHub hub, final @NotNull SentryOhosOptions options) {
        options
            .getLogger()
            .log(SentryLevel.DEBUG, "AnrIntegration enabled: %s", options.isAnrEnabled());

        if (options.isAnrEnabled()) {
            synchronized (WATCH_DOG_LOCK) {
                if (anrWatchDog == null) {
                    options
                        .getLogger()
                        .log(
                            SentryLevel.DEBUG,
                            "ANR timeout in milliseconds: %d",
                            options.getAnrTimeoutIntervalMillis());

                    anrWatchDog =
                        new ANRWatchDog(
                            options.getAnrTimeoutIntervalMillis(),
                            options.isAnrReportInDebug(),
                            error -> reportAnr(hub, options.getLogger(), error),
                            options.getLogger(),
                            context);
                    anrWatchDog.start();

                    options.getLogger().log(SentryLevel.DEBUG, "AnrIntegration installed.");
                }
            }
        }
    }

    /**
     * reportAnr
     *
     * @param hub IHub
     * @param logger ILogger
     * @param error ApplicationNotResponding
     */
    @TestOnly
    public void reportAnr(
        final @NotNull IHub hub,
        final @NotNull ILogger logger,
        final @NotNull ApplicationNotResponding error) {
        logger.log(SentryLevel.INFO, "ANR triggered with message: %s", error.getMessage());

        final Mechanism mechanism = new Mechanism();
        mechanism.setType("ANR");
        final ExceptionMechanismException throwable =
            new ExceptionMechanismException(mechanism, error, error.getThread(), true);

        hub.captureException(throwable);
    }

    @TestOnly
    @Nullable
    public ANRWatchDog getAnrWatchDog() {
        return anrWatchDog;
    }

    @Override
    public void close() throws IOException {
        synchronized (WATCH_DOG_LOCK) {
            if (anrWatchDog != null) {
                anrWatchDog.interrupt();
                anrWatchDog = null;
                if (options != null) {
                    options.getLogger().log(SentryLevel.DEBUG, "AnrIntegration removed.");
                }
            }
        }
    }
}
